package com.cherry.i18n.config;

import com.cherry.i18n.core.ClassPathResourceBundleMessageSource;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.AutoConfigureOrder;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.*;
import org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.context.MessageSourceProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.core.Ordered;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.util.ConcurrentReferenceHashMap;
import org.springframework.util.StringUtils;

import java.time.Duration;
import java.util.HashMap;
import java.util.Map;

/**
 * {@link EnableAutoConfiguration Auto-configuration} for {@link MessageSource}.
 *
 * @author Dave Syer
 * @author Phillip Webb
 * @author Eddú Meléndez
 */
@Configuration
@ConditionalOnMissingBean(name = AbstractApplicationContext.MESSAGE_SOURCE_BEAN_NAME, search = SearchStrategy.CURRENT)
@AutoConfigureOrder(Ordered.LOWEST_PRECEDENCE)
@Conditional(CherryMessageSourceAutoConfiguration.ResourceBundleCondition.class)
@EnableConfigurationProperties
@AutoConfigureBefore(MessageSourceAutoConfiguration.class)
public class CherryMessageSourceAutoConfiguration {

	private static final Resource[] NO_RESOURCES = {};
	private static final String SPRING_MESSAGES_BASENAME = "spring.messages.basename";
	private static final String COMMON_ERROR_CODE_SOURCE = "i18n/common_errorcode";

	public CherryMessageSourceAutoConfiguration(ConfigurableEnvironment environment) {
		configMessagesBaseName(environment);
	}

	@Bean
	@ConfigurationProperties(prefix = "spring.messages")
	public MessageSourceProperties messageSourceProperties() {
		return new MessageSourceProperties();
	}

	@Bean(AbstractApplicationContext.MESSAGE_SOURCE_BEAN_NAME)
	public MessageSource messageSource(Environment environment) {
		// I18nEnvironmentPostProcessor覆盖的配置不生效，这里覆盖下
		MessageSourceProperties properties = messageSourceProperties();
		ClassPathResourceBundleMessageSource messageSource = new ClassPathResourceBundleMessageSource();
		if (StringUtils.hasText(properties.getBasename())) {
			messageSource.setBasenames(StringUtils.commaDelimitedListToStringArray(
					StringUtils.trimAllWhitespace(properties.getBasename())));
		}
		if (properties.getEncoding() != null) {
			messageSource.setDefaultEncoding(properties.getEncoding().name());
		}
		messageSource.setFallbackToSystemLocale(properties.isFallbackToSystemLocale());
		Duration cacheDuration = properties.getCacheDuration();
		if (cacheDuration != null) {
			messageSource.setCacheMillis(cacheDuration.toMillis());
		}
		messageSource.setAlwaysUseMessageFormat(properties.isAlwaysUseMessageFormat());
		messageSource.setUseCodeAsDefaultMessage(properties.isUseCodeAsDefaultMessage());
		return messageSource;
	}

	private void configMessagesBaseName(ConfigurableEnvironment environment) {
		String messagesBasename = environment.getProperty(SPRING_MESSAGES_BASENAME);
		if (StringUtils.isEmpty(messagesBasename)) {
			messagesBasename = COMMON_ERROR_CODE_SOURCE;
		} else {
			messagesBasename = messagesBasename.endsWith(",") ? messagesBasename + COMMON_ERROR_CODE_SOURCE :
					messagesBasename + "," + COMMON_ERROR_CODE_SOURCE;
		}

		Map<String, Object> configMap = new HashMap<>();
		configMap.put(SPRING_MESSAGES_BASENAME, messagesBasename);
		MapPropertySource mapPropertySource = new MapPropertySource("i18n", configMap);
		environment.getPropertySources().addFirst(mapPropertySource);
	}

	protected static class ResourceBundleCondition extends SpringBootCondition {

		private static ConcurrentReferenceHashMap<String, ConditionOutcome> cache = new ConcurrentReferenceHashMap<>();

		@Override
		public ConditionOutcome getMatchOutcome(ConditionContext context,
				AnnotatedTypeMetadata metadata) {
			String defaultBaseName = COMMON_ERROR_CODE_SOURCE + ",messages";
			String basename = context.getEnvironment()
					.getProperty(SPRING_MESSAGES_BASENAME, defaultBaseName);
			ConditionOutcome outcome = cache.get(basename);
			if (outcome == null) {
				outcome = getMatchOutcomeForBasename(context, basename);
				cache.put(basename, outcome);
			}
			return outcome;
		}

		private ConditionOutcome getMatchOutcomeForBasename(ConditionContext context,
				String basename) {
			ConditionMessage.Builder message = ConditionMessage
					.forCondition("ResourceBundle");
			for (String name : StringUtils.commaDelimitedListToStringArray(
					StringUtils.trimAllWhitespace(basename))) {
				for (Resource resource : getResources(context.getClassLoader(), name)) {
					if (resource.exists()) {
						return ConditionOutcome
								.match(message.found("bundle").items(resource));
					}
				}
			}
			return ConditionOutcome.noMatch(
					message.didNotFind("bundle with basename " + basename).atAll());
		}

		private Resource[] getResources(ClassLoader classLoader, String name) {
			String target = name.replace('.', '/');
			try {
				return new PathMatchingResourcePatternResolver(classLoader)
						.getResources("classpath*:" + target + ".properties");
			}
			catch (Exception ex) {
				return NO_RESOURCES;
			}
		}

	}

}